/**************************************************************************\
 FILE ..........: BEEPER.C
 AUTHOR ........: Vitaly Puzrin
 DESCRIPTION ...: Alarm buzzer (lost aircraft locator) firmware
 NOTES .........: For non commercial use only.
 COPYRIGHT .....: Vitaly Puzrin, 2000
 HISTORY .......: DATE        COMMENT                    
                  ---------------------------------------------------
                  11.05.2000  First version.
\**************************************************************************/

#include "12c508.h"

#define BYTE    unsigned char
#define WORD    unsigned long       // !! 16 bit in this C compiler

#define clear_cfg       &= 0, |= 0x03E0
#define MCLR_OFF        &= ~0x10
#define CP_OFF          |= 0x08
#define WDT_OFF         &= ~0x4
#define OSC_INTERNAL    |= 0x02

#pragma    config clear_cfg, MCLR_OFF, CP_OFF,  WDT_OFF, OSC_INTERNAL

#define TIMER_OVERFLOW  26    // Defines system timer rateÿ - 150 Hz (6ms)
#define PULSE_ONE_MAX   3
#define PULSE_ZERO_MAX  6

#define CYCLE   16      // Measuring cycle length (in 1mHz ticks)
#define PULSE_MIN       (700/CYCLE)     // Min. default possible pulse len.
#define PULSE_MAX       (2200/CYCLE)    // Max. default possible pulse len.
#define ALARM_PERIOD    90  // Sound alsrm pause (used to measure pulses)
#define BAD_PULSE_MAX   4   // Max bad pulses count duging alarm pause
                            // (if more, then alarm turns on)
#define GOOD_PULSE_MIN  15  // Min good pulses count duging alarm pause
                            // (if less, then alarm turns on)
#define CLBR_TIME    30
#define CLBR_BAD_PULSE_MAX  8
#define CLBR_GOOD_PULSE_MIN  1

#define INPUT   GP0       // Control pulses input
#define OUT1    GP4       // Buzzer output 1
#define OUT2    GP5       // Buzzer output 2 (inverted, to get more power)

BYTE BadPulseCnt;   // "bad pulses" counter
BYTE GoodPulseCnt;  // "good pulses" counter

// Control pulse bounds are stored here
BYTE byPulseMin;    // min. pulse bound
BYTE byPulseMax;    // max. pulse bound

#define FILTER_LEN  8   // Filter len
#define FILTER_EXP  3   // Fliter len exp. (for fast division)

BYTE byFilter[FILTER_LEN];  // Filter data aray
BYTE byFilterPtr;
WORD wFilterSum;            // Filter data sum
BYTE byFilterOut;           // Filter output (sum? divided by 8)

BYTE byTmp;

#define ALARM_ZONE  3

/**************************************************************************\
    Tone generation
      Entry: byPer - wave period, byPerCnt - wave count
      Exit:  -
\**************************************************************************/
void Sound(BYTE byPer, BYTE wPerCnt )
{
    for( ; wPerCnt; wPerCnt-- )
    {
        byTmp = byPer;
        do { nop(); nop(); byTmp--; } while( byTmp!=0 );

        OUT1 = 1;
        OUT2 = 0;

        byTmp = byPer;
        do { nop(); nop(); byTmp--; } while( byTmp!=0 );

        OUT1 = 0;
        OUT2 = 1;
    }
}

/**************************************************************************\
    Siren sound
      Entry:  -
      Exit:   -
\**************************************************************************/
void AlarmSound(void)
{
    BYTE i;

    for( i=80; i>20; i--)
        Sound( i, 15 );

    for( i=20; i<80; i++)
        Sound( i, 15 );

    for( i=80; i>20; i--)
        Sound( i, 15 );

    for( i=20; i<80; i++)
        Sound( i, 15 );
}

/**************************************************************************\
    Filter reset
      Entry: -
      Exit:  -
\**************************************************************************/
void filter_init(void)
{
    // Zero filter contents
    for(byTmp=0; byTmp<FILTER_LEN; byTmp++)
        byFilter[byTmp] = 0;

    // Zero all internal variables
    byFilterPtr = 0;
    wFilterSum = 0;
    byFilterOut = 0;
}

/**************************************************************************\
    Putting new data to filter
      Entry: byNewData - new data to put into filter
      Exit:  buFilterOut - filter output
\**************************************************************************/
void ProcessFilter(BYTE byNewData)
{
    wFilterSum -= byFilter[byFilterPtr];// Take off old data
    byFilter[byFilterPtr] = byNewData;  // Take in new data
    wFilterSum += byNewData;   

    if ((++byFilterPtr) == FILTER_LEN)
        byFilterPtr = 0;

    // Let's calculate filter output
    // We round result
//    byFilterOut = (wFilterSum + (FILTER_LEN >> 1)) >> FILTER_EXP;
    byFilterOut = wFilterSum >> FILTER_EXP;
//    wTmp = wFilterSum + (FILTER_LEN >> 1);
//    byFilterOut = wTmp >> FILTER_EXP;
}

/**************************************************************************\
    Pulses measuring. Counting "good" and "bad" pulses
      Entry: byMeasurementTime - measurement time.
      Âûõîä: -
\**************************************************************************/
void CountPulses(BYTE byMeasurementTime)
{
    BYTE PulseCnt;  // Pulse length counter.
#define OvfCnt  byTmp
//    BYTE OvfCnt;    // Timer overflow counter. Used for rude count of
                    // "ones" & "zeroes" in control pulses

    BadPulseCnt = 0;
    GoodPulseCnt = 0;
    TMR0 = 0;

    for(;;)
    {
        //-------------------------------------
        // Wait riseing front (pulse start)
        OvfCnt = 0;
        while(!INPUT)
        {
            // Generate system time pulses (150 Hz)
            if (TMR0 >= TIMER_OVERFLOW) {
                byMeasurementTime--;
                OvfCnt++;
                TMR0 = 0;
            }
            
            if (OvfCnt > PULSE_ZERO_MAX)
            {
                BadPulseCnt++;
                OvfCnt = 0;
            }
            
            if (!byMeasurementTime)
                return;
        }

        PulseCnt = 0;
        //-------------------------------------
        // Measure pulse length. We wait falling front,
        // but no longer, than 2500us
        while(INPUT)
        {
            // Generate system time pulses (150 Hz)
            if (TMR0 >= TIMER_OVERFLOW) {
                byMeasurementTime--;
                TMR0 = 0;
            }
            else {
                nop();    // That's requied to make
                nop();    // both "if" branches equal.
                nop();
            }
        
            PulseCnt++;
        
            // ifPulse is too long, then finish measureing
            // with error.
            if (PulseCnt > byPulseMax)
            {
                PulseCnt = 0;
                break;
            }
        }

        //-------------------------------------
        // Determin if pulse is "good" or "bad"
        // and increment appropriate counter.
        if ((PulseCnt < byPulseMin) || (PulseCnt > byPulseMax))
            BadPulseCnt++;
        else
            GoodPulseCnt++;

        ProcessFilter(PulseCnt);
            
        // Wait intil pulse is low
        // (if measurement was terminated with error).
        OvfCnt = 0;
        while(INPUT)
        {
            // Generate system time pulses (150 Hz)
            if (TMR0 >= TIMER_OVERFLOW) {
                byMeasurementTime--;
                OvfCnt = 0;
                TMR0 = 0;
            }
            
            if (OvfCnt > PULSE_ONE_MAX)
            {
                BadPulseCnt++;
                OvfCnt = 0;
            }

            if (!byMeasurementTime)
                return;
        }
    }
}

/**************************************************************************\
    Program entry point.
\**************************************************************************/
void main(void)
{
//#define i   byPrevPulse     // Dirty trick, we save one RAM byte here
    BYTE i;
    BYTE byPrevPulse = 0;

    OPTION = 0x07;
    TRIS = 0x0B;
    GPIO = 0;
    
    byPulseMin = PULSE_MIN;
    byPulseMax = PULSE_MAX;
    filter_init();

    for(i=0; i<5; i++)
        Sound( 40, 100 );   // Tell us, that power is on.

    // Silent zone calibration
    // !!! Throttle stick must be at minimum
    // At first, calculate current stick pulse
    i = 0;
    for(;;)
    {
        CountPulses(CLBR_TIME);
        if (byPrevPulse > byFilterOut)
            byTmp = byPrevPulse - byFilterOut;
        else
            byTmp = byFilterOut - byPrevPulse;
        
        if (byTmp > 1)
        {
            if ((byFilterOut <= byPulseMax) &&
                (byFilterOut >= byPulseMin) &&
                (BadPulseCnt <= CLBR_BAD_PULSE_MAX) &&
                (GoodPulseCnt >= CLBR_GOOD_PULSE_MIN))
            {
                if (++i > 10)
                    break;
            }
            else
            {
                i = 0;
                byPrevPulse = 0;
            }
        }
        else
        {
            i = 0;
            byPrevPulse = byFilterOut;
        }
    }

    // Tell us, that calibration passed
    for(i=0; i<5; i++)
        Sound( 40, 100 );   
    for(i=0; i<5; i++)
        Sound( 60, 66 );
    for(i=0; i<5; i++)
        Sound( 40, 100 );
    
    // We assume, that stick is at minimum, so we
    // can say if channel is inverted or not.
    // We set pulse bounds in such way, that sirene
    // will sound only at minimum throttle.
    if (byFilterOut > (1500/CYCLE))
        byPulseMax = byFilterOut - ALARM_ZONE;
    else
        byPulseMin = byFilterOut + ALARM_ZONE;


    // Wait 30 seconds before arming pulses guard.
    TMR0 = 0;
    for(i=30; i>0; i--)
    {
        // Delay for 1 second
        for(byPrevPulse=15; byPrevPulse>0; byPrevPulse--) 
        {
            while(TMR0 != 0xFF);    // That's for 0.065 sec (15 Hz)
            TMR0++;
        }
    }
    
    // Infinit loop. Check pulses and turn sirene on
    // if signal is bad
    for(;;)
    {
        CountPulses(ALARM_PERIOD);
        if ((GoodPulseCnt < GOOD_PULSE_MIN) ||
            (BadPulseCnt > BAD_PULSE_MAX))
            AlarmSound();
    }
}



